//
//  NSObject+ZMKVO.m
//  ZMKitDemo
//
//  Created by zm on 2018/6/20.
//  Copyright © 2018年 zm. All rights reserved.
//
#import "NSObject+ZMKVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import "ZMObserverInfo.h"

#define ZMKVOClassPrefix @"ZMKVO_"
#define ZMAssociateArrayKey @"ZMAssociateArrayKey"

@implementation NSObject (ZMKVO)

#pragma mark - Public

- (void)zm_addObserver:(id)observer
                   key:(NSString *)key
              callback:(ZMKVOCallBack)callback {
    
    //检查对象的类有没有相应的setter方法，如果没有，抛出异常
    SEL setterSel = NSSelectorFromString([self setterForGetter:key]);
    Method setterMethod = class_getInstanceMethod([self class], setterSel);
    if (!setterMethod) {
        NSLog(@"找不到setter方法，无法添加KVO");
        return;
    }
    
    //检查对象 isa 指向的类是不是一个 KVO 类，如果不是，新建一个继承原来的类的子类，并把 isa 指向这个新建的子类
    Class class = object_getClass(self);
    NSString *className = NSStringFromClass(class);
    if (![className hasPrefix:ZMKVOClassPrefix]) {
        class = [self zm_KVOClassWithOriginalClassName:className];
        object_setClass(self, class);
    }
    
    //为kvoClass添加setter方法实现
    const char *types = method_getTypeEncoding(setterMethod);
    class_addMethod(class, setterSel, (IMP)zm_setter, types);
    
    // 添加该观察者到观察者列表中
    // 创建观察者的信息
    ZMObserverInfo *info = [[ZMObserverInfo alloc] initWithObserver:observer key:key callback:callback];
    //获取关联对象(装着所有监听者的数组)
    NSMutableArray *observers = objc_getAssociatedObject(self, ZMAssociateArrayKey);
    if (!observers) {
        observers = [NSMutableArray array];
        objc_setAssociatedObject(self, ZMAssociateArrayKey, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    [observers addObject:info];
}


- (void)zm_removeObserver:(id)observer key:(NSString *)key {
    NSMutableArray *observers = objc_getAssociatedObject(self, ZMAssociateArrayKey);
    if (!observers) return;
    
    for (ZMObserverInfo *info in observers) {
        if([info.key isEqualToString:key]) {
            [observers removeObject:info];
            break;
        }
    }
}

#pragma mark - Private
/**
 获取kvoclass
 */
- (Class)zm_KVOClassWithOriginalClassName:(NSString *)className {
    NSString *kvoClassName = [ZMKVOClassPrefix stringByAppendingString:className];
    Class kvoClass = NSClassFromString(kvoClassName);
    //如果kvoClass存在,则返回
    if (kvoClass) {
        return kvoClass;
    }
    //如果不存在，则创建
    Class originClass = object_getClass(self);
    kvoClass = objc_allocateClassPair(originClass, kvoClassName.UTF8String, 0);
    
    //修改kvoClass方法的实现
    Method classMethod = class_getInstanceMethod(kvoClass, @selector(class));
    const char *types = method_getTypeEncoding(classMethod);
    class_addMethod(kvoClass, @selector(class), (IMP)zm_class, types);
    
    objc_registerClassPair(kvoClass);
    
    return kvoClass;
}

/**
 模仿Apple的做法, 欺骗人们这个kvo类还是原类
 */
Class zm_class(id self,SEL _cmd) {
    Class class = object_getClass(self);
    Class superClass = class_getSuperclass(class);
    return  superClass;
}


/**
 根据getter方法名返回setter方法名
 */
- (NSString *)setterForGetter:(NSString *)key {
    //name -> Name -> setName
    //首字母转换成大写
    unichar c = [key characterAtIndex:0];
    NSString *str = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[NSString stringWithFormat:@"%c",c-32]];
    //增加set
    NSString *setter = [NSString stringWithFormat:@"set%@:",str];
    return setter;
}

/**
 *  根据setter方法名返回getter方法名
 */
- (NSString *)getterForSetter:(NSString *)key
{
    // setName: -> Name -> name
    
    //去掉set
    NSRange range = [key rangeOfString:@"set"];
    NSString *subStr1 = [key substringFromIndex:range.location + range.length];
    
    //首字母转换成小写
    unichar c = [subStr1 characterAtIndex:0];
    NSString *subStr2 = [subStr1 stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[NSString stringWithFormat:@"%c", c+32]];
    
    //去掉最后的:
    NSRange range2 = [subStr2 rangeOfString:@":"];
    NSString *getter = [subStr2 substringToIndex:range2.location];
    
    return getter;
}

static void zm_setter(id self, SEL _cmd, id newValue) {
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = [self getterForSetter:setterName];
    
    if (!getterName) {
        NSLog(@"找不到getter方法");
    }
    
    id oldValue = [self valueForKey:getterName];
    //调用原类的setter方法
    struct objc_super superClazz = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    
    ((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);

    // 找出观察者的数组, 调用对应对象的callback
    NSMutableArray *observers = objc_getAssociatedObject(self, ZMAssociateArrayKey);
    // 遍历数组
    for (ZMObserverInfo *info in observers) {
        if ([info.key isEqualToString:getterName]) {
            // gcd异步调用callback
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                info.callback(info.observer, getterName, oldValue, newValue);
            });
        }
    }
}
@end

